//
//  SQLiteFsRepository.cpp
//  FuseSQLite
//
//  Created by Admin on 7/20/14.
//  Copyright (c) 2014 Admin. All rights reserved.
//

#include "SQLiteFsRepository.h"

#include <sqlite3.h>

#include <stdexcept>
#include <cstring>
#include <string>
#include <memory>
#include <sstream>

void FinilizeSqliteStmt(sqlite3_stmt* stmt)
{
    sqlite3_finalize(stmt);
}

SQLiteFsRepository::SQLiteFsRepository(const char* dbPath)
: m_db(nullptr)
{
    int error = sqlite3_open_v2(dbPath, &m_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
    if (error != SQLITE_OK)
    {
        throw std::runtime_error("SQL can't open file");
        sqlite3_close(m_db);
    }
}

SQLiteFsRepository::~SQLiteFsRepository()
{
    sqlite3_close(m_db);
}

bool SQLiteFsRepository::doesHardLinksTableExisits()
{
    return doesTableExist("hardLinks");
}

bool SQLiteFsRepository::doesFoldersTableExists()
{
    return doesTableExist("folders");
}

void SQLiteFsRepository::createHardLinksTable()
{
    const char createTableQuery[] = "CREATE TABLE hardLinks (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, nodeType INTEGER, nodeId INTEGER, attributes INTEGER, parentId INTEGER)";
    executeQuery(createTableQuery);
}

void SQLiteFsRepository::createFoldersTable()
{
    const char createTableQuery[] = "CREATE TABLE folders (id INTEGER PRIMARY KEY AUTOINCREMENT, dummy INTEGER)";
    executeQuery(createTableQuery);
}

int SQLiteFsRepository::addFolder()
{
    executeQuery("INSERT INTO Folders (dummy) VALUES (0)");
    
    return sqlite3_last_insert_rowid(m_db);
}

int SQLiteFsRepository::addFolderLink(const char* name, int folderId, int attributes, int parentFolderId)
{
    std::ostringstream queryStream;
    queryStream << "INSERT INTO hardLinks (name, nodeType, nodeId, attributes, parentId) VALUES ("
                << "'" << name << "', "
                << HardLink::kFolder << ", "
                << folderId << ", "
                << attributes << ", "
                << parentFolderId << ")";
    
    executeQuery(queryStream.str().c_str());
    
    return sqlite3_last_insert_rowid(m_db);
}

int SQLiteFsRepository::getFoldersCount()
{
    std::shared_ptr<sqlite3_stmt> queryStmt = executeQuery("SELECT COUNT(*) FROM folders");

    int folderCount = sqlite3_column_int(queryStmt.get(), 0);
    return folderCount;
}

std::vector<HardLink> SQLiteFsRepository::getHardLinks()
{
    std::vector<HardLink> hardLinks;
    std::shared_ptr<sqlite3_stmt> stmt = executeQuery("SELECT name, nodeType, nodeId, attributes, parentId FROM hardLinks");
    
    while (SQLITE_ROW == sqlite3_errcode(m_db))
    {
        HardLink hardLink;
        hardLink.name = (char*)sqlite3_column_text(stmt.get(), 0);
        hardLink.nodeType = (HardLink::Type)sqlite3_column_int(stmt.get(), 1);
        hardLink.nodeId = sqlite3_column_int(stmt.get(), 2);
        hardLink.attributes = sqlite3_column_int(stmt.get(), 3);
        hardLink.parentId = sqlite3_column_int(stmt.get(), 4);
        
        hardLinks.push_back(hardLink);
        
        sqlite3_step(stmt.get());
    }
    
    return hardLinks;
}

HardLink SQLiteFsRepository::getHardLinkInfo(int linkId)
{
    std::ostringstream quesryString;
    quesryString << "SELECT name, nodeType, nodeId, attributes, parentId FROM hardLinks WHERE id = " << linkId;
    
    std::shared_ptr<sqlite3_stmt> stmt = executeQuery(quesryString.str().c_str());
    
    HardLink hardLink;
    hardLink.name = (char*)sqlite3_column_text(stmt.get(), 0);
    hardLink.nodeType = (HardLink::Type)sqlite3_column_int(stmt.get(), 1);
    hardLink.nodeId = sqlite3_column_int(stmt.get(), 2);
    hardLink.attributes = sqlite3_column_int(stmt.get(), 3);
    hardLink.parentId = sqlite3_column_int(stmt.get(), 4);
    
    return hardLink;
}

#pragma mark - private

bool SQLiteFsRepository::doesTableExist(const char* tableName)
{
    std::string selectCommand = "SELECT * FROM ";
    selectCommand += tableName;
    
    sqlite3_stmt* selectStmt = nullptr;
    
    int error = sqlite3_prepare_v2(m_db, selectCommand.c_str(), -1, &selectStmt, NULL);
    const bool result = (error == SQLITE_OK);
    
    sqlite3_finalize(selectStmt);
    
    return result;
}

std::shared_ptr<sqlite3_stmt> SQLiteFsRepository::executeQuery(const char* queryStr)
{
    sqlite3_stmt* executeQueryStmt = nullptr;
    
    int error = sqlite3_prepare_v2(m_db, queryStr, -1, &executeQueryStmt, NULL);
    std::shared_ptr<sqlite3_stmt> stmt(executeQueryStmt, FinilizeSqliteStmt);
    
    if (error != SQLITE_OK)
    {
        throw std::runtime_error("SQL can't create stmt");
    }
    
    error = sqlite3_step(executeQueryStmt);
    if (error != SQLITE_DONE && error != SQLITE_ROW)
    {
        throw std::runtime_error("SQL can't execute stmt");
    }
    
    return stmt;
}
